#include "LocationCursorByDescription.h"

#include <cstdio>

#include <libdwarf/dwarf.h>

namespace dwarf {

const void *LocationCursorByDescription::toLittle(Dwarf_Small _operation) {
	static char little[32] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31};
	return &little[_operation - DW_OP_lit0];
}

LocationCursorByDescription::LocationCursorByDescription(const void *_baseAddress,
														 Dwarf_Debug _debug,
														 Dwarf_Locdesc *_locationDescriptions)
		: baseAddress_(_baseAddress),
		  debug_(_debug),
		  locationDescriptions_(_locationDescriptions),
		  locationDescriptionIndex_(0),
		  address_(NULL),
		  piece_(0) {
}

LocationCursorByDescription::~LocationCursorByDescription() {
}

Location LocationCursorByDescription::fetch(Dwarf_Unsigned _byteSize) {
	if (address_ != NULL) {
		const void *address = address_;
		address_ = (const char *) address_ + _byteSize;
		return Location(Location::TYPE_ADDRESS, address, _byteSize);
	}
	if (piece_ != 0) {
		if (piece_ < _byteSize) {
			fprintf(stderr, "[ERR] (%s, %d) %s\n", __FILE__, __LINE__, __FUNCTION__);
			return Location(Location::TYPE_ERROR);
		}
		piece_ -= _byteSize;
		return Location(Location::TYPE_OPTIMIZED_OUT);
	}
	if (locationDescriptionIndex_ == locationDescriptions_->ld_cents) {
		return Location(Location::TYPE_END);
	}
	Dwarf_Loc *location = &locationDescriptions_->ld_s[locationDescriptionIndex_++];
	if (location->lr_atom == DW_OP_addr) {
		address_ = (const void *) location->lr_number;
		const void *address = address_;
		address_ = (const char *) address_ + _byteSize;
		return Location(Location::TYPE_ADDRESS, address, _byteSize);
	}
	else if (location->lr_atom == DW_OP_const1u || location->lr_atom == DW_OP_const1s) {
		if (locationDescriptionIndex_ == locationDescriptions_->ld_cents) {
			fprintf(stderr, "[ERR] (%s, %d) %s\n", __FILE__, __LINE__, __FUNCTION__);
			return Location(Location::TYPE_ERROR);
		}
		Dwarf_Loc *location1 = &locationDescriptions_->ld_s[locationDescriptionIndex_++];
		if (location1->lr_atom == DW_OP_stack_value) {
			if (locationDescriptionIndex_ == locationDescriptions_->ld_cents) {
				return Location(Location::TYPE_ADDRESS, &location->lr_number, 1);
			}
			Dwarf_Loc *location2 = &locationDescriptions_->ld_s[locationDescriptionIndex_++];
			if (location2->lr_atom == DW_OP_piece) {
				if (location2->lr_number != _byteSize) {
					fprintf(stderr, "[ERR] (%s, %d) %s\n", __FILE__, __LINE__, __FUNCTION__);
					return Location(Location::TYPE_ERROR);
				}
				return Location(Location::TYPE_ADDRESS, &location->lr_number, 1);
			}
			else {
				fprintf(stderr, "[ERR] (%s, %d) %s\n", __FILE__, __LINE__, __FUNCTION__);
				return Location(Location::TYPE_ERROR);
			}
		}
		else {
			fprintf(stderr, "[ERR] (%s, %d) %s\n", __FILE__, __LINE__, __FUNCTION__);
			return Location(Location::TYPE_ERROR);
		}
	}
	else if (location->lr_atom == DW_OP_const2u || location->lr_atom == DW_OP_const2s) {
		if (locationDescriptionIndex_ == locationDescriptions_->ld_cents) {
			fprintf(stderr, "[ERR] (%s, %d) %s\n", __FILE__, __LINE__, __FUNCTION__);
			return Location(Location::TYPE_ERROR);
		}
		Dwarf_Loc *location1 = &locationDescriptions_->ld_s[locationDescriptionIndex_++];
		if (location1->lr_atom == DW_OP_stack_value) {
			if (locationDescriptionIndex_ == locationDescriptions_->ld_cents) {
				return Location(Location::TYPE_ADDRESS, &location->lr_number, 2);
			}
			Dwarf_Loc *location2 = &locationDescriptions_->ld_s[locationDescriptionIndex_++];
			if (location2->lr_atom == DW_OP_piece) {
				if (location2->lr_number != _byteSize) {
					fprintf(stderr, "[ERR] (%s, %d) %s\n", __FILE__, __LINE__, __FUNCTION__);
					return Location(Location::TYPE_ERROR);
				}
				return Location(Location::TYPE_ADDRESS, &location->lr_number, 2);
			}
			else {
				fprintf(stderr, "[ERR] (%s, %d) %s\n", __FILE__, __LINE__, __FUNCTION__);
				return Location(Location::TYPE_ERROR);
			}
		}
		else {
			fprintf(stderr, "[ERR] (%s, %d) %s\n", __FILE__, __LINE__, __FUNCTION__);
			return Location(Location::TYPE_ERROR);
		}
	}
	else if (location->lr_atom == DW_OP_const4u || location->lr_atom == DW_OP_const4s) {
		if (locationDescriptionIndex_ == locationDescriptions_->ld_cents) {
			fprintf(stderr, "[ERR] (%s, %d) %s\n", __FILE__, __LINE__, __FUNCTION__);
			return Location(Location::TYPE_ERROR);
		}
		Dwarf_Loc *location1 = &locationDescriptions_->ld_s[locationDescriptionIndex_++];
		if (location1->lr_atom == DW_OP_stack_value) {
			if (locationDescriptionIndex_ == locationDescriptions_->ld_cents) {
				return Location(Location::TYPE_ADDRESS, &location->lr_number, 4);
			}
			Dwarf_Loc *location2 = &locationDescriptions_->ld_s[locationDescriptionIndex_++];
			if (location2->lr_atom == DW_OP_piece) {
				if (location2->lr_number != _byteSize) {
					fprintf(stderr, "[ERR] (%s, %d) %s\n", __FILE__, __LINE__, __FUNCTION__);
					return Location(Location::TYPE_ERROR);
				}
				return Location(Location::TYPE_ADDRESS, &location->lr_number, 4);
			}
			else {
				fprintf(stderr, "[ERR] (%s, %d) %s\n", __FILE__, __LINE__, __FUNCTION__);
				return Location(Location::TYPE_ERROR);
			}
		}
		else {
			fprintf(stderr, "[ERR] (%s, %d) %s\n", __FILE__, __LINE__, __FUNCTION__);
			return Location(Location::TYPE_ERROR);
		}
	}
	else if (location->lr_atom == DW_OP_const8u || location->lr_atom == DW_OP_const8s) {
		if (locationDescriptionIndex_ == locationDescriptions_->ld_cents) {
			fprintf(stderr, "[ERR] (%s, %d) %s\n", __FILE__, __LINE__, __FUNCTION__);
			return Location(Location::TYPE_ERROR);
		}
		Dwarf_Loc *location1 = &locationDescriptions_->ld_s[locationDescriptionIndex_++];
		if (location1->lr_atom == DW_OP_stack_value) {
			if (locationDescriptionIndex_ == locationDescriptions_->ld_cents) {
				return Location(Location::TYPE_ADDRESS, &location->lr_number, 8);
			}
			Dwarf_Loc *location2 = &locationDescriptions_->ld_s[locationDescriptionIndex_++];
			if (location2->lr_atom == DW_OP_piece) {
				if (location2->lr_number != _byteSize) {
					fprintf(stderr, "[ERR] (%s, %d) %s\n", __FILE__, __LINE__, __FUNCTION__);
					return Location(Location::TYPE_ERROR);
				}
				return Location(Location::TYPE_ADDRESS, &location->lr_number, 8);
			}
			else {
				fprintf(stderr, "[ERR] (%s, %d) %s\n", __FILE__, __LINE__, __FUNCTION__);
				return Location(Location::TYPE_ERROR);
			}
		}
		else {
			fprintf(stderr, "[ERR] (%s, %d) %s\n", __FILE__, __LINE__, __FUNCTION__);
			return Location(Location::TYPE_ERROR);
		}
	}
	else if (location->lr_atom >= DW_OP_lit0 && location->lr_atom <= DW_OP_lit31) {
		if (locationDescriptionIndex_ == locationDescriptions_->ld_cents) {
			fprintf(stderr, "[ERR] (%s, %d) %s\n", __FILE__, __LINE__, __FUNCTION__);
			return Location(Location::TYPE_ERROR);
		}
		Dwarf_Loc *location1 = &locationDescriptions_->ld_s[locationDescriptionIndex_++];
		if (location1->lr_atom == DW_OP_stack_value) {
			if (locationDescriptionIndex_ == locationDescriptions_->ld_cents) {
				return Location(Location::TYPE_ADDRESS, toLittle(location->lr_atom), 1);
			}
			Dwarf_Loc *location2 = &locationDescriptions_->ld_s[locationDescriptionIndex_++];
			if (location2->lr_atom == DW_OP_piece) {
				if (location2->lr_number != _byteSize) {
					fprintf(stderr, "[ERR] (%s, %d) %s\n", __FILE__, __LINE__, __FUNCTION__);
					return Location(Location::TYPE_ERROR);
				}
				return Location(Location::TYPE_ADDRESS, toLittle(location->lr_atom), 1);
			}
			else {
				fprintf(stderr, "[ERR] (%s, %d) %s\n", __FILE__, __LINE__, __FUNCTION__);
				return Location(Location::TYPE_ERROR);
			}
		}
		else {
			fprintf(stderr, "[ERR] (%s, %d) %s\n", __FILE__, __LINE__, __FUNCTION__);
			return Location(Location::TYPE_ERROR);
		}
	}
	else if (location->lr_atom == DW_OP_fbreg) {
		address_ = (const char *) baseAddress_ + location->lr_number;
		const void *address = address_;
		address_ = (const char *) address_ + _byteSize;
		return Location(Location::TYPE_ADDRESS, address, _byteSize);
	}
	else if (location->lr_atom == DW_OP_piece) {
		piece_ = location->lr_number;
		if (piece_ < _byteSize) {
			fprintf(stderr, "[ERR] (%s, %d) %s\n", __FILE__, __LINE__, __FUNCTION__);
			return Location(Location::TYPE_ERROR);
		}
		piece_ -= _byteSize;
		return Location(Location::TYPE_OPTIMIZED_OUT);
	}
	else if (location->lr_atom == DW_OP_implicit_value) {
		if (locationDescriptionIndex_ == locationDescriptions_->ld_cents) {
			fprintf(stderr, "[ERR] (%s, %d) %s\n", __FILE__, __LINE__, __FUNCTION__);
			return Location(Location::TYPE_ERROR);
		}
		Dwarf_Loc *location1 = &locationDescriptions_->ld_s[locationDescriptionIndex_++];
		if (location1->lr_atom == DW_OP_piece) {
			if (location1->lr_number != _byteSize) {
				fprintf(stderr, "[ERR] (%s, %d) %s\n", __FILE__, __LINE__, __FUNCTION__);
				return Location(Location::TYPE_ERROR);
			}
			return Location(Location::TYPE_ADDRESS, (const void *) location->lr_number2, location->lr_number);
		}
		else {
			fprintf(stderr, "[ERR] (%s, %d) %s\n", __FILE__, __LINE__, __FUNCTION__);
			return Location(Location::TYPE_ERROR);
		}
	}
	else if (location->lr_atom == DW_OP_GNU_implicit_pointer) {
		return Location(Location::TYPE_SYNTHETIC_POINTER);
	}
	else {
		return Location(Location::TYPE_OPTIMIZED_OUT);
	}
}

}
